/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Access Point Service base - All services should interface through this base file.
 * \author Antti Maula <antti.maula@tkk.fi>
 */

#define _GNU_SOURCE
#include <string.h>
#include "protocolDispatcher.h"
#include "protocol/fastProtocolHdr.h"
#include "protocol/hubctrlHdr.h"
#include "hubctrl_common.h"
#include "apsecurity.h"
#include "owndebug.h"
#include "ownutils.h"
#include "msgreplybuffer.h"
#include "apservicebase.h"
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>



//*****************************************************************************
//*****************************************************************************

typedef enum EnumRegisterAPServiceState {
  KRegisterAPStateUnknown = 0,
  KRegisterAPStateRegistered = 2,
  KRegisterAPStateFailedIdReserved = 3,
  KRegisterAPStateFailedAuth = 4,
  KRegisterAPStateFailedExpired = 5
} ERegisterAPServiceState;
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
static volatile int run = 1;
static ownThreadHandle signalHandlerThread = NULL;
static ownThreadHandle protocolDispProcessorThreadHandle = NULL;
static uint32_t requested_hubId = 0x00;
static uint32_t requested_serviceId = 0x00;
static uint8_t *hubctrl_secret_key = NULL;
static unsigned int hubctrl_secret_key_size = 0;
static uint32_t messageSeq = 0;
static uint32_t localNodeId = 0;
static char *serviceName = NULL;
static unsigned int wait_timeout = 2000;

//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
fastProtocolHandlerFuncPtr fastProtocolHandlerFunc = NULL;
hubctrlProtocolHandlerFuncPtr hubctrlProtocolHandlerFunc = NULL;
serviceInitHandlerFuncPtr serviceInitHandlerFunc = NULL;
serviceDeinitHandlerFuncPtr serviceDeinitHandlerFunc = NULL;
getQueryStatsFuncPtr getQueryStatsFunc = NULL;
//*****************************************************************************

static TMsgReplyBuffer gMsgReplyBuffer;
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

uint32_t apservicebaseGetLocalNodeId(void)
{
  return localNodeId;
}
//*****************************************************************************

void apservicebaseSetServiceInfo(const char const *name, 
                                 const uint32_t nodeid,
                                 const uint32_t typeid)
{
  requested_hubId = (nodeid & 0xFF);
  requested_serviceId = typeid;

  const int l = strlen(name)+1;
  serviceName = malloc(l);
  memcpy(serviceName, name, l);
}
//*****************************************************************************

void apservicebaseSetHubCtrlKey(const uint8_t const *key, const unsigned int size)
{
  if (hubctrl_secret_key) free(hubctrl_secret_key);
  hubctrl_secret_key = malloc(size);
  assert(hubctrl_secret_key != NULL);
  memcpy(hubctrl_secret_key, key, size);
  hubctrl_secret_key_size = size;
}
//*****************************************************************************

void apservicebaseSetDefaultOperationTimeout(const unsigned int timeout_ms)
{
  wait_timeout = timeout_ms;
}
//*****************************************************************************

void apservicebaseRegisterFastProtocolHandler(fastProtocolHandlerFuncPtr fptr)
{
  fastProtocolHandlerFunc = fptr;
}
//*****************************************************************************

void apservicebaseRegisterHubCtrlProtocolHandler(hubctrlProtocolHandlerFuncPtr fptr)
{
  hubctrlProtocolHandlerFunc = fptr;
}
//*****************************************************************************

void apservicebaseRegisterServiceInitHandler(serviceInitHandlerFuncPtr fptr)
{
  serviceInitHandlerFunc = fptr;
}
//*****************************************************************************

void apservicebaseRegisterServiceDeinitHandler(serviceDeinitHandlerFuncPtr fptr)
{
  serviceDeinitHandlerFunc = fptr;
}
//*****************************************************************************

void apservicebaseRegisterGetQueryStats(getQueryStatsFuncPtr fptr)
{
  getQueryStatsFunc = fptr;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

static int protocolDispProcessorThread(void *p)
{
  long unsigned int old_query_sum = 0;
  ownTime_ms_t lastupdate = ownTime_get_ms();

  TProtocolDisp *link = (TProtocolDisp *)p;
  while(run) {
    if (protocolDispProcess(link, 1000)) {
      dPrint(ODERROR,"Process returned error!");
      run = 0;
    }

    if (getQueryStatsFunc && ownTime_get_ms_since(lastupdate) > 5000) {
      
      long unsigned int s_count = 0;
      long unsigned int f_count = 0;
      
      getQueryStatsFunc(&s_count, &f_count);
      
      const long unsigned int new_query_sum = s_count + f_count;
      const long unsigned int delta_sum = new_query_sum - old_query_sum;
        
      if (delta_sum > 10) {
        dPrint(ODSTATUS,"Stats: %lu succesfull, and %lu failed queries processed so far",
               s_count,
               f_count);
        old_query_sum = new_query_sum;
        lastupdate = ownTime_get_ms();
      }
    }
  }
  return 0;
}
//*****************************************************************************

static void fastProtocolCallback(int protocol_number,
                                 const unsigned char *data,
                                 int size,
                                 void *userPtr)
{
  // This function gets called when a FastProtocol packet is received
  // from Hub.
  const TFastProtocolHeader *frame = (const TFastProtocolHeader *)data;

  if (size >= sizeof(TFastProtocolHeader) &&
      size == frame->protocol_id.total_size) {
    // Protocol size match; We have enough data to process call
    
    if (fastProtocolHandlerFunc) {
      fastProtocolHandlerFunc(frame,
                              data+sizeof(TFastProtocolHeader),
                              size-sizeof(TFastProtocolHeader),
                              userPtr);
    } else {
      dPrint(ODWARN,"WARNING: Received FastProtocol message, but no handler is registered!");

    }

  } else {
    dPrint(ODWARN,"Warning: Received invalid FastProtocol frame!");
    
  }
}
//*****************************************************************************

static void processQueryLocalNodeIdReply(const THubCtrlProtocolHeader *frame,
                                         const unsigned int size,
                                         TProtocolDisp *link)
{
  if (size == sizeof(THubCtrlProtocolHeader)+sizeof(THubCtrlSubHeaderNodeInfo)) {
    const THubCtrlSubHeaderNodeInfo *ni = (THubCtrlSubHeaderNodeInfo *)(frame+1);
    
    dPrint(ODVINFO,"Own address for this module is '0x%08x' (Link delay: %u us)",
           ni->nodeid, ni->link_delay_us);

    msgreplybufferNotifyReply(&gMsgReplyBuffer, frame->message_id, (void*)(long int)(ni->nodeid));
    
  } else {
    dPrint(ODWARN,"QueryLocalNodeId: Invalid reply!");

  }
}
//*****************************************************************************

static void processRegisterAPServiceReply(const THubCtrlProtocolHeader *frame,
                                          const unsigned int size,
                                          TProtocolDisp *link)
{
  const uint8_t *data = (void*)frame;
  // RegisterAP received. Introduce a few variables to make
  // code more readable (Compiler should optimize these out)
  const unsigned int bsize = sizeof(THubCtrlProtocolHeader);
  const unsigned int rsize = sizeof(THubCtrlSubHeaderRegisterAPService);
  const unsigned int asize = sizeof(THubCtrlSubHeaderAuthentication);
  ERegisterAPServiceState regapsstate = KRegisterAPStateUnknown;
 
  if (size < bsize) {
    dPrint(ODERROR,"Too small reply frame!");
    return;
  }

  dPrint(ODVINFO,"Received a reply for RegisterAPService Id=%u, operation %s\n",
         frame->message_id,
         frame->result==KHubCtrlResultOK ? "was a success!" :
         frame->result==KHubCtrlResultIdReserved ? "failed due to reserved ID." :
         frame->result==KHubCtrlResultAuthExpired ? "failed due to expired authorization." :
         "produced an unknown error!");
  
  if (frame->result==KHubCtrlResultOK) regapsstate = KRegisterAPStateRegistered;
  else if (frame->result==KHubCtrlResultIdReserved) regapsstate = KRegisterAPStateFailedIdReserved;
                                         
  if (size == bsize+rsize+asize) {
    const THubCtrlSubHeaderRegisterAPService *rap = (void*)(data+bsize); 
    const THubCtrlSubHeaderAuthentication *auth = (void*)(data+bsize+rsize); 

    dPrint(ODVINFO, "RegisterAPService Reply Id=%u contained request for NodeID: 0x%08x\n",
           frame->message_id, rap->requested_id);
    
    const int a = hubctrlcommonAuthenticateMessage(data, size, auth, 
                                                   (void*)hubctrl_secret_key, hubctrl_secret_key_size, 
                                                   10);
    if (a == 0) {
      dPrint(ODVINFO,"RegisterAPService Reply Id=%u was succesfully Authenticated\n",frame->message_id);
 
    } else if (a == 1) {
      dPrint(ODVINFO,"RegisterAPService Reply Id=%u, received reply had expired!\n", frame->message_id);
      regapsstate = KRegisterAPStateFailedExpired;
      
    } else {
      dPrint(ODVINFO,"RegisterAPService Reply Id=%u failed to Authenticate!!!\n", frame->message_id);
      regapsstate = KRegisterAPStateFailedAuth;

    }

  } else if (size == bsize+rsize) {
    const THubCtrlSubHeaderRegisterAPService *rap = (void*)(data+bsize); 
    dPrint(ODVINFO,"RegisterAPService Reply Id=%u contained request for NodeID: 0x%08x\n",
           frame->message_id, rap->requested_id);

  } else if (size == bsize) {
    dPrint(ODVINFO,"RegisterAPService Reply Id=%u contained no additional headers.\n",
           frame->message_id);

  }

  // Abusing the reply pointer with single value ;)
  msgreplybufferNotifyReply(&gMsgReplyBuffer,frame->message_id, (void*)regapsstate);
}
//*****************************************************************************

static void hubctrlProtocolCallback(int protocol_number,
                                    const unsigned char *data,
                                    int size,
                                    void *userPtr)
{
  // This function gets called when a HubCtrlProtocol packet is received
  // from Hub.
  TProtocolDisp *link = (TProtocolDisp *)userPtr;
  const THubCtrlProtocolHeader *frame = (const THubCtrlProtocolHeader *)data;
  
  //  dPrintLCRed(ODTEST,"Received a HubCtrlProtocolCallback!");

  if (size >= sizeof(THubCtrlProtocolHeader) &&
      size == frame->protocol_id.total_size) {
    if (frame->flags & KHubCtrlFlagReply) {
      // Protocol size match; We have enough data to process call
      if (frame->request == KHubCtrlRequestQueryLocalNodeId) {
        // Process QueryLocalNodeIdReply
        processQueryLocalNodeIdReply(frame, size, link);

      } else if (frame->request == KHubCtrlRequestRegisterAPService) {
        // Process RegisterAPServiceReply
        processRegisterAPServiceReply(frame, size, link); 
        
      } else {
        dPrint(ODERROR,"Unknown Reply.");
        
      }
      
    } else {
      // Not a reply.
      if (frame->request == KHubCtrlRequestClientEcho) {

        // Send AS-IS back to AP.
        protocolDispSendPacket(link, (void*)data, size);
        
        // Sent
        //        dPrint(ODTEST,"Replied to ClientEcho!");
      } else if (hubctrlProtocolHandlerFunc) {
        // Not ours - Hence pass to Service level
        hubctrlProtocolHandlerFunc(frame, userPtr);

      }
    }
  } else {
    dPrint(ODWARN,"Warning: Received invalid HubCtrlProtocol frame!");
    
  }
}
//*****************************************************************************

static int RegisterService(TProtocolDisp *link)
{
  int result = 0;
  int r;
  const unsigned int totsize = ( sizeof(THubCtrlProtocolHeader) + 
                                 sizeof(THubCtrlSubHeaderRegisterAPService) + 
                                 sizeof(THubCtrlSubHeaderAuthentication) );
  uint8_t buffer[totsize];
  THubCtrlProtocolHeader *frame = (THubCtrlProtocolHeader *)buffer;
  THubCtrlSubHeaderRegisterAPService *rap = (THubCtrlSubHeaderRegisterAPService *)(frame+1);
  THubCtrlSubHeaderAuthentication *auth = (THubCtrlSubHeaderAuthentication *)(rap+1);
  
  double begin = ownTime_get_s();
  // Assign identification information
  protocolIdentificationConstructHeader(&frame->protocol_id,
                                        HUBCTRLPROTOCOL_NUMBER,
                                        totsize);
  
  // Assign base parameters
  frame->request = KHubCtrlRequestRegisterAPService;
  frame->flags = 0x00;
  frame->subheader_count = 2;
  frame->result = 0;
  frame->message_id = ++messageSeq;
  
  // Assign Request parameter
  rap->subtype = KHubCtrlSubtypeRegisterAPService;
  rap->subsize = sizeof(THubCtrlSubHeaderRegisterAPService);
  rap->requested_id = requested_hubId;
  rap->service_type = requested_serviceId;

  // Assign authentication data
  auth->subtype = KHubCtrlSubtypeAuthentication;
  auth->subsize = sizeof(THubCtrlSubHeaderAuthentication);
  memset(auth->sha1, 0x00, sizeof(auth->sha1));
  auth->timestamp_s = time(NULL);
         
  // Now; calculate authentication data SHA-1; via separate buffer.
  uint8_t sha1[20];
  unsigned int sha1_size = 20;
  apsecurityCalculateHMAC(sha1,
                          &sha1_size,
                          (uint8_t*)hubctrl_secret_key,
                          hubctrl_secret_key_size,
                          buffer,
                          totsize);
  assert(sha1_size == 20);
  memcpy(auth->sha1, sha1, sizeof(sha1));


  // Complete. SEND!
  r = msgreplybufferInsertMsg(&gMsgReplyBuffer,messageSeq, NULL);
  assert(r == 0);
  protocolDispSendPacket(link, (void*)frame, totsize);

  TMsgReplyBufferEntry mrbe;
  result = -1;
  if (msgreplybufferWaitReply(&gMsgReplyBuffer,&mrbe, messageSeq, wait_timeout) == 0) {
    ERegisterAPServiceState s = (ERegisterAPServiceState)mrbe.reply;
    switch(s) {
    case KRegisterAPStateUnknown:
      dPrint(ODSTATUS,"%s failed to register as GIMnetAP Service - Unknown reason!", serviceName);
      break;
    case KRegisterAPStateRegistered:
      dPrint(ODSTATUS,"%s succesfully registered as GIMnetAP Service", serviceName);
      result = 0;
      break;
    case KRegisterAPStateFailedIdReserved:
      dPrint(ODSTATUS,"%s failed to register as GIMnetAP Service - ID reserved.", serviceName);
      break;
    case KRegisterAPStateFailedAuth:
      dPrint(ODSTATUS,"%s failed to register as GIMnetAP Service - Authentication failed.", serviceName);
      break;
    case KRegisterAPStateFailedExpired:
      dPrint(ODSTATUS,"%s failed to register as GIMnetAP Service - Authentication Expired.", serviceName);
      break;
    }
    
  } else {
    dPrint(ODERROR,"Timeout waiting for Reply from GIMnetAP for RegisterAPService Query - Please try again!");
    
  }

  // Now, result check, and act upon.
  if (result == 0) {
    dPrint(ODSTATUS,"RegisterAPService Operation completed in %.2f ms", 
           1000.0*(ownTime_get_s()-begin));
  
  } else {
    dPrint(ODSTATUS,"RegisterAPService Operation failed! (Attempt took %.2f ms). Cannot continue.", 
           1000.0*(ownTime_get_s()-begin));
    return result;
  
  }
  
  /////////////////////////////////////////////////////////////////////
  ///////// NOW; check for own ID.
  
  begin = ownTime_get_s();
  // Copy parameters
  frame->protocol_id.total_size = sizeof(THubCtrlProtocolHeader);
  frame->request = KHubCtrlRequestQueryLocalNodeId;
  frame->flags = 0x00;
  frame->subheader_count = 0;
  frame->result = 0;
  frame->message_id = ++messageSeq;
  
  r = msgreplybufferInsertMsg(&gMsgReplyBuffer,messageSeq, NULL);
  protocolDispSendPacket(link, (void*)frame, frame->protocol_id.total_size);

  if (msgreplybufferWaitReply(&gMsgReplyBuffer,&mrbe, messageSeq, wait_timeout) == 0) {
    localNodeId = (long int)mrbe.reply;
    dPrint(ODSTATUS,"Query for OwnId resulted in '0x%08x'", localNodeId);
    
  } else {
    dPrint(ODSTATUS,"Timeout waiting for Reply from GIMnetAP for QueryLocalNodeId Query - Please try again!");
    result = -1;
  }

  if (result == 0) {
    dPrint(ODSTATUS,"QueryLocalNodeId Operation completed in %.2f ms", 
           1000.0*(ownTime_get_s()-begin));
  
  } else {
    dPrint(ODSTATUS,"QueryLocalNodeId Operation failed! (Attempt took %.2f ms). Cannot continue.", 
           1000.0*(ownTime_get_s()-begin));
  
  }
  
  return result;

}
//*****************************************************************************

static int InitializeBase(TProtocolDisp *link)
{
  int result;

  // Initialize MsgReplyBuffer
  msgreplybufferInit(&gMsgReplyBuffer);
  
  // Register Callbacks
  result = protocolDispRegisterCallback(link, FASTPROTOCOL_NUMBER, 
                                        fastProtocolCallback, link);
  result |= protocolDispRegisterCallback(link, HUBCTRLPROTOCOL_NUMBER, 
                                         hubctrlProtocolCallback, link);

  // Start processor
  protocolDispProcessorThreadHandle = ownThread_Create((void*)protocolDispProcessorThread, link);

  return result;
}
//*****************************************************************************

static void DeinitializeBase(TProtocolDisp *link)
{
  // .. and deinit
  msgreplybufferDeinit(&gMsgReplyBuffer);
}
//*****************************************************************************

static int InitializeService(TProtocolDisp *link)
{
  int result = -1;
  if (serviceInitHandlerFunc) {
    result = serviceInitHandlerFunc(link);

  } else {
    dPrint(ODWARN,"Warning: InitializeService called, but no handler registered!");

  }
  return result;
}
//*****************************************************************************

static void DeinitializeService(TProtocolDisp *link)
{
  if (serviceDeinitHandlerFunc) {
    serviceDeinitHandlerFunc(link);
    
  } else {
    dPrint(ODWARN,"Warning: DeinitializeService called, but no handler registered!");
    
  }
  return;
}
//*****************************************************************************

int apservicebaseExecuteService(TProtocolDisp *link)
{
  int result = 0;

  if (hubctrl_secret_key == NULL) {
    dPrint(ODERROR,"ERROR: HubCtrl Key not set! Will not continue.");
    return -1;
  } else if (serviceName == NULL) {
    dPrint(ODERROR,"ERROR: ServiceInfo not set (Name == NULL)! Will not continue.");
    return -1;
  }


  // Initialize Base
  result = InitializeBase(link);

  if (!result) {
    // Initialize Service
    run = InitializeService(link) == 0;
    
    if (run) {
      //// Start operation
      run = RegisterService(link) == 0;
      
      if (run) {
        assert((localNodeId & 0x000FFFF) == (requested_hubId & 0x0000FFFF));
        dPrint(ODSTATUS,"Succesfully connected to GIMnetAP with NodeID: 0x%08x",
               localNodeId);
        dPrint(ODSTATUS,"Waiting for commands.");
      }
    }
      
    // Now start waiting. (Everything is handled by callbacks)
    ownThread_Join(protocolDispProcessorThreadHandle, &result);
    
    // Do Deinit - Service
    DeinitializeService(link);

    // Do Deinit - Base
    DeinitializeBase(link);
  }
    
  return result;
}
//*****************************************************************************

static int SignalHandlerThread(void *t)
{
  dPrint(8, "Signal handler thread (%ld) starting...", pthread_self());

  while (run) {
    // Now, wait for requested signals to arrive
    int sig;
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGPIPE);
    sigaddset(&mask, SIGHUP);
    sigwait(&mask, &sig);

    // Got signal, sigwait returned!
    dPrint(ODSTATUS,"Got Signal '%d' (%s)", sig, strsignal(sig));

    // What to do?
    switch(sig) {
    case SIGINT:
    case SIGTERM:
    case SIGPIPE:
    case SIGHUP:
      run = 0;
      dPrint(ODSTATUS,"Terminating %s - this may take up to 5 seconds to complete...", serviceName);
      break;
      
    default:
      dPrint(ODSTATUS,"Unhandled signal! Ignore!");
      break;
    }
  }
  return 0;
}
//*****************************************************************************

void apservicebaseInitSignalHandler(void)
{
  // Now; Set to BLOCK ALL SIGNALS
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);
  sigaddset(&mask, SIGTERM);
  sigaddset(&mask, SIGPIPE);
  sigaddset(&mask, SIGHUP);
  pthread_sigmask(SIG_BLOCK, &mask, NULL);
  
  // Start signal handler thread
  signalHandlerThread = ownThread_Create((void*)SignalHandlerThread, NULL);
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
